package com.fredhat.tvshowrenamer;

import java.io.File;
import java.io.IOException;
import java.util.*;

import org.apache.log4j.Logger;
import org.jdom.*;
import org.jdom.input.SAXBuilder;

/**
 * The front end class for the XML configuration.  For full details on 
 * the configuration contents, please access the project site's wiki at
 * http://code.google.com/p/tvshowrenamer/w/list
 * @author fred.drake
 *
 */
public class Configuration {
	/**
	 * The full location of the main configuration file.
	 */
	public static final String CONF_FILE = System.getProperty("app.dir")+
			File.separator+"conf"+File.separator+"conf.xml";

	private static Configuration instance;
	
	private Logger logger;
	private long sleepTime;
	private String incomingDir;
	private String outgoingDir;
	private String template;
	private boolean recursiveDirectories;
	private TVShow[] tvShows;
	private String[] episodeRegexes;
	private String[] dateRegexes;
	private boolean dryRun;
	private boolean separateSeasons;
	private ForbiddenCharacters[] forbiddenCharacters;

	// Reads the XML file into the instance variables
	private Configuration() {
		logger = Logger.getLogger(this.getClass());
		Document doc = null;
		File file = new File(CONF_FILE);
		if (!file.exists()) {
			logger.fatal("Conf file "+file+" does not exist.");
			System.exit(1);
		}
		logger.info("Reading configuration from file");
		SAXBuilder builder = new SAXBuilder();
		try {
			doc = builder.build(file);
		} catch (Exception e) {
			logger.fatal("Cannot read the configuration file "+
					file, e);
			System.exit(1);
		}
		
		Element root = doc.getRootElement();
		try {
			sleepTime = Long.parseLong(root.getChildText("sleeptime"));
		} catch (NumberFormatException e) {
			logger.warn("<sleeptime> element non-numeric in configuration file.  Using default.");
			sleepTime = 30000;
		}
		incomingDir = root.getChildText("incomingdir");
		outgoingDir = root.getChildText("outgoingdir");
		template = root.getChildText("template");
		recursiveDirectories = Boolean.parseBoolean(root.getChildText("recursivedirectories"));
		dryRun = Boolean.parseBoolean(root.getChildText("dryrun"));
		separateSeasons = Boolean.parseBoolean(root.getChildText("separateseasons"));
		
		Element episodeRegexesElem = root.getChild("episoderegexes");
		episodeRegexes = new String[episodeRegexesElem.getChildren("regex").size()];
		for(int i=0; i<episodeRegexesElem.getChildren("regex").size(); i++) {
			episodeRegexes[i] = ((Element)episodeRegexesElem.getChildren(
					"regex").get(i)).getText();
		}
		
		Element tvShowsElem = root.getChild("tvshows");
		tvShows = new TVShow[tvShowsElem.getChildren("tvshow").size()];
		for(int i=0; i<tvShowsElem.getChildren("tvshow").size(); i++) {
			Element tvShowElem = (Element)tvShowsElem.getChildren("tvshow").get(i);
			String name = tvShowElem.getChildText("name");
			
			Element showRegexesElem = tvShowElem.getChild("showregexes");
			String[] showRegexes = new String[showRegexesElem.getChildren("regex").size()];
			for(int j=0; j<showRegexesElem.getChildren("regex").size(); j++) {
				showRegexes[j] = ((Element)showRegexesElem.getChildren(
						"regex").get(j)).getText();
			}
			
			String extension = tvShowElem.getChildText("extension");			
			String tvComURL = tvShowElem.getChildText("tvcomurl");

			boolean thisShowSeparateSeasons = true;
			if (tvShowElem.getChildText("separateseasons") == null || 
					tvShowElem.getChildText("separateseasons").equals("")) {
				thisShowSeparateSeasons = separateSeasons; // Default value
			} else {
				thisShowSeparateSeasons = Boolean.parseBoolean(
						tvShowElem.getChildText("separateseasons"));
			}

			String template = this.template;
			if (tvShowElem.getChildText("template") != null && 
					!tvShowElem.getChildText("template").equals("")) {
				template = tvShowElem.getChildText("template");
			}
			tvShows[i] = new TVShow(name, tvComURL, showRegexes, extension, 
					thisShowSeparateSeasons, template);
		}

		Element dateRegexesElem = root.getChild("dateregexes");
		dateRegexes = new String[dateRegexesElem.getChildren("regex").size()];
		for(int i=0; i<dateRegexesElem.getChildren("regex").size(); i++) {
			dateRegexes[i] = ((Element)dateRegexesElem.getChildren(
					"regex").get(i)).getText();
		}
		
		Element forbiddenCharactersElem = root.getChild("forbiddencharacters");
		forbiddenCharacters = new ForbiddenCharacters[forbiddenCharactersElem.getChildren(
				"forbidden").size()];  
		for(int i=0; i<forbiddenCharactersElem.getChildren("forbidden").size(); i++) {
			Element fcElem = (Element) forbiddenCharactersElem.getChildren(
					"forbidden").get(i);
			forbiddenCharacters[i] = new ForbiddenCharacters(
					fcElem.getChildText("regex"), fcElem.getChildText("changeto"));
		}
	}

	/**
	 * Obtains the Configuration singleton instance
	 * @return the Configuration instance
	 */
	public static Configuration getConfiguration() {
		if (instance == null) {
			instance = new Configuration();
		}
		
		return instance;
	}

	/**
	 * Gets the <sleeptime/> XML element value
	 * @return the time to sleep in milliseconds, or 0 to disable
	 */
	public long getSleepTime() {
		return sleepTime;
	}
	
	/**
	 * Gets the <incomingdir/> XML element value
	 * @return the incoming directory for file processing
	 */
	public String getIncomingDir() {
		return incomingDir;
	}
	
	/**
	 * Gets the <outgoingdir/> XML element value
	 * @return the outgoing directory for file processing
	 */
	public String getOutgoingDir() {
		return outgoingDir;
	}
	
	/**
	 * Gets whether or not to process the incoming directory recursively, from
	 * the <recursivedirectories/> XML element value.
	 * @return true if a deep scan is performed on the incoming directory,
	 * false otherwise.
	 */
	public boolean useRecursiveDirectories() {
		return recursiveDirectories;
	}

	/**
	 * Gets the list of regular expressions used to parse season and episode
	 * values from filenames, from the <episoderegexes/> XML element and all of
	 * the children <regex/> elements.
	 * @return the regular expressions used for season/episode parsing
	 */
	public String[] getEpisodeRegexes() {
		return episodeRegexes;
	}

	/**
	 * Gets the <tvshows/> XML element and all of the children <tvshow/> elements
	 * and returns the data in object form.
	 * @return the information for all TV shows that the application will consider
	 */
	public TVShow[] getTVShows() {
		return tvShows;
	}

	/**
	 * Gets the list of regular expressions used to parse date values from filenames,
	 * from the <dateregexes/> XML element and all of the children <regex/> elements.
	 * @return the regular expressions used for date parsing
	 */
	public String[] getDateRegexes() {
		return dateRegexes;
	}

	/**
	 * Gets whether or not this processing will be done as a dry run, as defined by
	 * the <dryrun/> XML element.  A dry run will process the same way, except files
	 * will not be renamed, and the log file will instead explain what would have been
	 * done.  This is useful for testing your configuration.
	 * @return true if processing will actually be done, false otherwise
	 */
	public boolean isDryRun() {
		return dryRun;
	}

	/**
	 * Gets the forbidden characters as defined by the <forbiddencharacters/> XML
	 * element and all children <forbidden/> elements, in object form.
	 * @return the forbidden character structures as defined in the configuration file
	 */
	public ForbiddenCharacters[] getForbiddenCharacters() {
		return forbiddenCharacters;
	}
	
	/**
	 * Determines if files will be placed in directories separated by season number.
	 * @return whether or not to separate parsed files by seasons
	 */
	public boolean separateSeasons() {
		return separateSeasons;
	}
	
	/**
	 * Gets the template format of the new filename.  The values are:
	 * %n - The name of the show
	 * %s - The season number, in two digit format
	 * %e - The episode number, in two digit format
	 * %t - The episode title
	 * %d - The air date, in YYYY-MM-DD format
	 * @return
	 */
	public String template() {
		return template;
	}

	/**
	 * This is the value object structure for the <forbiddencharacters/> XML element
	 * in the configuration file.  It identifies both the characters that should
	 * be changed (in regular expression form), and what characters to which each 
	 * of these should be changed.
	 * @author fred.drake
	 *
	 */
	class ForbiddenCharacters {
		private String regex;
		private String changeTo;
		
		public ForbiddenCharacters(String regex, String changeTo) {
			this.regex = regex;
			this.changeTo = changeTo;
		}
		
		public String getRegex() {
			return regex;
		}
		
		public String getChangeTo() {
			return changeTo;
		}
	}
}
